/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "customattributesview.h"
#include "attributesviewdelegate.h"
#include "attributesmodel.h"
#include "validatornode.h"
#include "attributecontainervalidator.h"
#include "utils.h"
#include "customnodesview.h"
#include "commands.h"
#include <QHeaderView>
#include <QKeyEvent>

CustomAttributesView::CustomAttributesView(CustomNodesView* nodesView, QWidget *parent):
    QTreeView (parent),
    nodesView(nodesView)
{
    setItemDelegate(new AttributesViewDelegate(this));
    setEditTriggers(QAbstractItemView::EditTrigger::AllEditTriggers);
    setSelectionMode(QAbstractItemView::SingleSelection);
    setAlternatingRowColors(true);
    header()->setStretchLastSection(true);

    connect(this, &CustomAttributesView::expanded, this, [this](const QModelIndex&){resizeColumnToContents(0);});

    // context menu
    setContextMenuPolicy(Qt::CustomContextMenu);
    connect(this, &CustomAttributesView::customContextMenuRequested, this, &CustomAttributesView::onCustomContextMenu);

    attributesContextMenu = new QMenu(this);

    // add attribute
    add_attribute_submenu = attributesContextMenu->addMenu(QIcon(":/icons/icons/resources/16x16/add.png"), "Add attribute...");
    // remove attribute
    remove_attribute = new QAction(QIcon(":/icons/icons/resources/16x16/delete.png"), "Remove attribute", attributesContextMenu);
    connect(remove_attribute, &QAction::triggered, this, &CustomAttributesView::removeAttribute);
    attributesContextMenu->addAction(remove_attribute);
    // remove attribute container
    remove_attribute_container = new QAction(QIcon(":/icons/icons/resources/16x16/delete.png"), "Remove attribute container", attributesContextMenu);
    connect(remove_attribute_container, &QAction::triggered, this, &CustomAttributesView::removeAttribute);
    attributesContextMenu->addAction(remove_attribute_container);
    expandAll();
}

void CustomAttributesView::keyPressEvent(QKeyEvent *event)
{
    if(event->type() == QKeyEvent::KeyPress)
    {
        if(event->matches(QKeySequence::Delete))
        {
            removeAttribute();
        }
    }
    QTreeView::keyPressEvent(event);
}

AttributesModel *CustomAttributesView::getAttributesModel()
{
    return static_cast<AttributesModel*>(model());
}

void CustomAttributesView::setUndoStack(QUndoStack *stack)
{
    undoStack = stack;
}

void CustomAttributesView::onCustomContextMenu(const QPoint &point)
{
    AttributesModel * model = getAttributesModel();
    if(model)
    {
        const QModelIndex& index = indexAt(point).siblingAtColumn(1); // get column 1 to know if its editable (column 0 is always not)

        const bool attr_editable = getAttributesModel()->isEditable(index);

        remove_attribute->setEnabled(index.isValid() && attr_editable);

        if(getAttributesModel()->isContainer(index))
        {
            AttributeContainer* attr_container = getAttributesModel()->getContainer(index);
            const bool container_editable = attr_container->getValidator()->isRemovable();
            remove_attribute_container->setEnabled(container_editable && index.isValid());
        }
        else
        {
            remove_attribute_container->setDisabled(true);
        }

        MemoryNode * node = model->getParentMemoryNode();

        const bool node_editable = node->isEditable();

        if(!node_editable)
            attributesContextMenu->setDisabled(true);

        add_attribute_submenu->clear();

        // attributes list
        std::function<void(QMenu*, AttributeContainerValidator*)> fn;
        fn = [&fn, node, this](QMenu* menu, AttributeContainerValidator* validator)
        {
            for(const auto& attribute: validator->getAttributes())
            {
                const QString& full_attr_name = QString::fromStdString(join(attribute->getFullName(), "/"));
                if(!node->getAttributeContainer()->hasAttribute(attribute->getFullName()))
                {
                    QAction* add_attr_action = new QAction(QString::fromStdString(attribute->getName()), menu);
                    add_attr_action->setEnabled(attribute->isAddable());
                    connect(add_attr_action, &QAction::triggered, this, [this, full_attr_name](){this->nodesView->addAttribute(full_attr_name);});
                    menu->addAction(add_attr_action);
                }
            }
            for(const auto& container: validator->getAttributeContainers())
            {
                QMenu* container_menu = menu->addMenu(QString::fromStdString(container->getName()));
                fn(container_menu, container.get());
                container_menu->setDisabled(container_menu->isEmpty());
            }
        };
        // top level
        AttributeContainerValidator* validator = node->getValidator()->getAttributes();
        fn(add_attribute_submenu, validator);
        add_attribute_submenu->setDisabled(add_attribute_submenu->isEmpty() || !node_editable);

        attributesContextMenu->exec(viewport()->mapToGlobal(point));
    }
}

void CustomAttributesView::removeAttribute()
{
    AttributesModel* model = getAttributesModel();
    if (!model)
        return;

    QModelIndexList indexes = selectionModel()->selectedIndexes();
    QList<QPersistentModelIndex> persistent_indexes;

    std::copy_if(std::make_move_iterator(indexes.begin()),
                 std::make_move_iterator(indexes.end()),
                 std::back_inserter(persistent_indexes),
                 [](const QModelIndex& index){return index.column() == 0;});

    for (const auto& index: persistent_indexes)
    {
        const QModelIndex& tree_node = model->getNodesModel()->getIndex(model->getParentMemoryNode());
        bool is_model_editable = model->getNodesModel()->isEditable(tree_node);

        // Remove the attribute container selected by the user in the models view
        if (model->isContainer(index))
        {
            AttributeContainer* attr_container = model->getContainer(index);
            if (is_model_editable && attr_container->getValidator()->isRemovable())
                undoStack->push(new RemoveAttributeContainerCommand(index, tree_node));

            continue;
        }

        // Skip it for non-attribute
        if (!model->isAttribute(index))
            continue;

        // Skip it for non-removable or non-editable attributes
        Attribute* attr = model->getAttribute(index);
        if (!attr->getValidator()->isRemovable() || !attr->isEditable())
            continue;

        // Remove the entire container if the attribute is the last remaining in the container
        AttributeContainer* parent_container = attr->getParent();
        bool is_attr_last = (parent_container->getTotalSize() <= 1);
        bool is_parent_removable = parent_container->getValidator()->isRemovable();
        if (is_model_editable && is_parent_removable && is_attr_last)
        {
            QModelIndex parent_index = model->getIndex(parent_container);
            undoStack->push(new RemoveAttributeContainerCommand(parent_index, tree_node));
            continue;
        }

        // Remove a single attribute if the parent container has more remaining attributes
        // or the parent container is unremovable
        undoStack->push(new RemoveAttributeCommand(index, tree_node));
    }
}
